Jerry's Log

Pair in JAVA

contents

Pair라는 개념은 매우 흔하지만, 제네릭 Pair 클래스는 자바의 표준 라이브러리(JDK)에 포함되어 있지 않습니다. 이는 종종 개발자들을 놀라게 하는 중요한 사실입니다. 대신, Pair는 개발자가 직접 만들거나 서드파티 라이브러리를 통해 사용하는, 두 개의 객체를 함께 담는 간단한 자료구조입니다.

Pair의 주된 목적은 자바에서 기본적으로 불가능한, 메서드에서 두 개의 값을 반환하는 편리한 방법을 제공하는 것입니다.


Pair 클래스가 해결하는 문제

자바 메서드는 단 하나의 값 또는 객체만 반환할 수 있습니다. 만약 한 번에 두 가지를 반환해야 한다면 어떻게 해야 할까요? 예를 들어, 배열에서 최솟값과 최댓값을 모두 찾는 메서드가 필요할 수 있습니다.

Pair 패턴 이전에는 개발자들이 다음과 같이 이상적이지 않은 해결책을 사용했습니다.

Pair 클래스는 제네릭을 사용하여 재사용 가능한 해결책을 제공합니다. 이는 간단한 두 요소 컨테이너 역할을 하여, 매번 특정 클래스를 새로 만들지 않고도 어떤 두 객체든 단일 단위로 묶어 반환할 수 있게 해줍니다.


나만의 Pair 클래스 만들기 📦

표준 라이브러리에 없기 때문에, 개발자나 프로젝트가 자신만의 Pair 구현을 갖는 것은 매우 일반적입니다. 좋은 최신 Pair 클래스는 제네릭을 사용하며 불변(immutable)입니다.

다음은 잘 설계된 완전한 예제입니다.

import java.util.Objects;

// 왼쪽(L)과 오른쪽(R) 타입에 제네릭을 사용합니다.
public final class Pair {

    private final L left;
    private final R right;

    // 정적 팩토리 메서드를 통한 생성을 강제하기 위해 private 생성자 사용
    private Pair(L left, R right) {
        this.left = left;
        this.right = right;
    }

    // 편리한 생성을 위한 정적 팩토리 메서드
    public static  Pair of(L left, R right) {
        return new Pair<>(left, right);
    }

    // 값에 대한 getter
    public L getLeft() {
        return left;
    }

    public R getRight() {
        return right;
    }

    // 컬렉션에서 올바르게 동작하도록 equals()와 hashCode() 재정의
    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        Pair pair = (Pair) o;
        return Objects.equals(left, pair.left) && Objects.equals(right, pair.right);
    }

    @Override
    public int hashCode() {
        return Objects.hash(left, right);
    }

    // 쉬운 디버깅을 위해 toString() 재정의
    @Override
    public String toString() {
        return "Pair{" +
                "left=" + left +
                ", right=" + right +
                '}';
    }
}

Pair 클래스 사용 방법

위 클래스를 사용하여, 이제 최솟값/최댓값 문제를 깔끔하게 해결할 수 있습니다.

public class ArrayUtils {

    public static Pair findMinMax(int[] numbers) {
        if (numbers == null || numbers.length == 0) {
            throw new IllegalArgumentException("배열은 비어 있을 수 없습니다.");
        }
        
        int min = numbers[0];
        int max = numbers[0];
        
        for (int number : numbers) {
            if (number < min) min = number;
            if (number > max) max = number;
        }
        
        // 두 개의 값을 하나의 Pair 객체로 감싸서 반환
        return Pair.of(min, max);
    }

    public static void main(String[] args) {
        int[] data = {5, 1, 9, 3, 7, 2, 8};
        
        Pair result = findMinMax(data);
        
        System.out.println("최솟값: " + result.getLeft());   // 출력: 최솟값: 1
        System.out.println("최댓값: " + result.getRight());  // 출력: 최댓값: 9
    }
}

Pair 클래스를 찾을 수 있는 곳

직접 작성하고 싶지 않다면, 몇몇 인기 있는 라이브러리에서 자체 구현을 제공합니다.


현대적인 대안: 레코드 (자바 16 이상) ✨

많은 경우, "두 개의 값을 반환"하는 문제를 해결하는 현대적인 방법은 **레코드(record)**를 사용하는 것입니다. 레코드는 불변의 데이터 전달 클래스를 만들기 위한 간결한 문법입니다.

제네릭 Pair를 만드는 대신, 거의 상용구 코드 없이 의미가 명확한 특정 레코드를 만들 수 있습니다.

최솟값/최댓값 예제에 레코드 사용:

// 단 한 줄로 레코드를 정의!
public record MinMaxResult(int min, int max) {}

이 한 줄은 자동으로 다음과 같은 것들을 갖춘 클래스를 생성합니다.

사용법:

public static MinMaxResult findMinMaxWithRecord(int[] numbers) {
    // ... 이전과 동일한 로직 ...
    return new MinMaxResult(min, max);
}

// --- main 메서드 내 ---
MinMaxResult result = findMinMaxWithRecord(data);
System.out.println("최솟값: " + result.min());
System.out.println("최댓값: " + result.max());

제네릭 Pair 클래스도 여전히 유용한 도구이지만, 반환하는 두 값이 명확하고 구체적인 의미를 갖는다면, 레코드가 종종 더 깔끔하고, 표현력이 좋으며, 현대적인 자바 접근 방식입니다.

references